package model.shader;

import java.awt.Color;
import java.util.Collection;

import javax.vecmath.Point2f;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;

import lib.common.ColorOperation;
import lib.common.Constants;
import model.Spec;
import model.lights.Light;
import model.primitive.IntersectionData;
import model.ray.Bouncer;
import model.ray.Ray;
import raytracing.RayTracer;


public class PhongShader implements Shader {
	public String name;
	public Color diff;
	public Spec spec;
	public int samples;
	private final static double diffCOEF = 2;
	private final static double specCOEF = 6;
	private final static double shinyness = 20;
	private boolean textured;
	private Color[] texels;
	private int wtext;
	private int htext;
	
	// mapa de bits
	
	public PhongShader(String name, Color diff, Spec spec, int samples ) {
		this.name = name;
		this.diff = new Color(diff.getRGB());
		this.spec = spec;
	}	
	
	public PhongShader(String name, Color diff, Spec spec, int samples, Color[] texels, int w, int h) {
		this.name = name;
		this.diff = new Color(diff.getRGB());
		this.spec = spec;
		this.texels = texels;
		this.textured=true;
		this.wtext=w;
		this.htext=h;
	}	

	@Override
	public String getName() {
		return this.name;
	}
	@Override
	public void setName(String name) {
		this.name = name;
	}
	
	@Override
	public Color getColor(Collection<Light> lights, IntersectionData state, RayTracer rt) {
		Color result, mix, diff, spec;
		boolean isLight = false;
		
		result = null;
		if (lights == null || lights.size() == 0 || state == null) {
			return Constants.DEF_COLOR;
		}
			
		for (Light l : lights) {
			isLight = shadowTest(state, l, rt);
			if (isLight) {
				diff = lambertianReflectance(l, state, rt);
				spec = specularReflectance(l, state);
				mix = ColorOperation.addColors(diff, spec);
			} else {
				mix = Constants.DEF_COLOR;
			}
			if (result != null) {
				result = ColorOperation.addColors(result, mix);
			} else {
				result = mix;
			}
		}
		
		
		//TODO: sumar la componente ambient de la luz q falta definir de donde sale.
		
		// devuelve el color base a asignar al punto de interseccion.
		return result;
	}
	
	
	private Color lambertianReflectance(Light l, IntersectionData state, RayTracer rt) {
		Vector3d shadowray; // para construccion del shadowray
		Color diff;
		float colorMultiplier;
		
		shadowray = new Vector3d();
		
		shadowray.sub(l.source, state.hitPoint);
		colorMultiplier = (float)state.getNormal().dot(shadowray);
		
		Vector3d infNormal = new Vector3d(state.getNormal());
		infNormal.negate();
		float colorMultiplier2 = (float)infNormal.dot(shadowray);

		if (colorMultiplier < 0 && colorMultiplier2 > 0) {
			colorMultiplier = colorMultiplier2;
		}
		
		if (colorMultiplier > 0) {
			double distanceToLight = shadowray.length();
			
			// TODO: Se tomo como solucion un numero arbitrario (diffCOEF=6) que imite
			// el ratio del la reflectancia difusa del objeto lo mas similar. Comparando resultados
			// con sunflow
			colorMultiplier *= ((l.power) / diffCOEF) * l.getAttenuation(distanceToLight);
			
			// mezcla las componentes de colores diffuse de la luz y el objeto
			
			if( textured ){
				diff = ColorOperation.blendColors(getTextureColor(state), l.diffuseColor);
			} else {
				diff = ColorOperation.blendColors(this.diff, l.diffuseColor);
			}
			
			diff = ColorOperation.multiplyColor(diff, colorMultiplier);
		} else {
			diff = Constants.DEF_COLOR;
		}
		
		return diff;
	}
	
	private Color getTextureColor(IntersectionData state) {
		
		Point2f p =state.getPrimitive().getUV(state.hitPoint);
		float u = p.x;
		float v = p.y;
		
		float fu =  ((u + 1000.5f) * wtext);
		float fv = (v + 1000f ) * wtext;
		int u1 = ((int)fu) % wtext;
		int v1 = ((int)fv) % htext;
		int u2 = (u1 + 1) % wtext;
		int v2 = (v1 + 1) % htext;
		
		float fracu = fu - (float)Math.floor(fu);
		float fracv = fv - (float)Math.floor(fv);
		
		float w1 = (1-fracu)*(1-fracv);
		float w2 = fracu * (1-fracv);
		float w3 = (1- fracu)* fracv;
		float w4 = fracu * fracv;
		
		Color c1 = texels[u1+v1*wtext];
		Color c2 = texels[u2+v1*wtext];
		Color c3 = texels[u1+v2*wtext];
		Color c4 = texels[u2+v2*wtext];
		
		c1=ColorOperation.multiplyColor(c1, w1);
		c2=ColorOperation.multiplyColor(c2, w2);
		c3=ColorOperation.multiplyColor(c3, w3);
		c4=ColorOperation.multiplyColor(c4, w4);
		
		c1=ColorOperation.addColors(c1, c2);
		c1=ColorOperation.addColors(c1, c3);
		c1=ColorOperation.addColors(c1, c4);
		return c1;
	}

	private Color specularReflectance(Light light, IntersectionData state) {
		 Vector3d v, l, r, n;
		 double multiplier;
		 Color result;
		 
		 /* Obtiene V, R y L
		  * V = vector desde la interseccion hacia la camara
		  * L = vector desde la interseccion hacia la luz
		  * R = vector reflejo de L sobre la normal del punto de interseccion
		 */
		 
		 v = state.intersectRay.getDirection();
		 
		 
		 l = new Vector3d();
		 l.sub(light.source, state.hitPoint);
		 l.normalize();
		 
		 n = new Vector3d(state.getNormal());
		 
		 
		 n.scale(2 * state.normal.dot(l));
		 
		 r = new Vector3d();
		 r.sub(l, n);
		 
		 multiplier = (float)r.dot(v);
		 
		 if (multiplier > 0) {
			 double distanceToLight = l.length();
			 multiplier = Math.pow(multiplier, shinyness);
			// TODO: Se hizo que el ratio sea spec.power / (specCOEF=2)
			 multiplier *= ((this.spec.power) / specCOEF)* light.getAttenuation(distanceToLight);
			 result = ColorOperation.blendColors(this.spec.color, light.diffuseColor);
			 result = ColorOperation.multiplyColor(result, multiplier);
		 } else {
			 result = Constants.DEF_COLOR;
		 }
		
		 return result;
	}
	
	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append("Shader\n");
		sb.append("type: phong");
		sb.append("name: " + name).append("\n");
		sb.append("spec: " + spec).append("\n");
		sb.append("diff: " + diff).append("\n");
		sb.append("samples: " + samples).append("\n");
		return sb.toString();
	}

	@Override
	public double getRefractionConstant() {
		return 0;
	}

	@Override
	public double getReflectionConstant() {
		return 0;
	}
	
	private boolean shadowTest(IntersectionData state, Light l, RayTracer rt) {
		Vector3d shadowray;
		// rayo que va del punto de interseccion a la luz
		shadowray = new Vector3d();
		
		shadowray.sub(l.source, state.hitPoint);
		
		//trace ray to light
		Vector3d direction = new Vector3d(shadowray);
		direction.normalize();
		//direction.negate();
		Point3d hitPoint = Bouncer.displaceOrigin(state.hitPoint, direction);
		Ray ray = new Ray(hitPoint, direction);
		IntersectionData istate = rt.traceRay(ray, rt.scene);
		
		boolean isLight = false;
		if( istate == null)	{
			isLight = true;
		} else {
			if (istate.primitive == null) {
				isLight = true;
			} else {
				isLight = false;
			}
		}
		
		return isLight;
	}
}

